En omfattande guide till TypeScript's kraftfulla Mapped Types och Conditional Types, med praktiska exempel och avancerade anvÀndningsfall för att skapa robusta och typsÀkra applikationer.
BemÀstra TypeScript's Mapped Types och Conditional Types
TypeScript, ett superset av JavaScript, erbjuder kraftfulla funktioner för att skapa robusta och underhÄllbara applikationer. Bland dessa funktioner utmÀrker sig Mapped Types och Conditional Types som vÀsentliga verktyg för avancerad typhandlÀggning. Denna guide ger en omfattande översikt av dessa koncept, utforskar deras syntax, praktiska tillÀmpningar och avancerade anvÀndningsfall. Oavsett om du Àr en erfaren TypeScript-utvecklare eller precis har börjat din resa, kommer den hÀr artikeln att utrusta dig med kunskapen för att utnyttja dessa funktioner effektivt.
Vad Àr Mapped Types?
Mapped Types lÄter dig skapa nya typer genom att omvandla befintliga. De itererar över egenskaperna i en befintlig typ och applicerar en omvandling pÄ varje egenskap. Detta Àr sÀrskilt anvÀndbart för att skapa variationer av befintliga typer, som att göra alla egenskaper valfria eller skrivskyddade.
GrundlÀggande syntax
Syntaxen för en Mapped Type Àr som följer:
type NewType<T> = {
[K in keyof T]: Transformation;
};
T: Indatatypen som du vill mappa över.K in keyof T: Itererar över varje nyckel i indatatypenT.keyof Tskapar en union av alla egenskapsnamn iT, ochKrepresenterar varje enskild nyckel under iterationen.Transformation: Omvandlingen du vill tillÀmpa pÄ varje egenskap. Detta kan vara att lÀgga till en modifierare (somreadonlyeller?), Àndra typen eller nÄgot helt annat.
Praktiska exempel
Göra egenskaper skrivskyddade
LÄt oss sÀga att du har ett interface som representerar en anvÀndarprofil:
interface UserProfile {
name: string;
age: number;
email: string;
}
Du kan skapa en ny typ dÀr alla egenskaper Àr skrivskyddade:
type ReadOnlyUserProfile = {
readonly [K in keyof UserProfile]: UserProfile[K];
};
Nu kommer ReadOnlyUserProfile att ha samma egenskaper som UserProfile, men de kommer alla att vara skrivskyddade.
Göra egenskaper valfria
PÄ samma sÀtt kan du göra alla egenskaper valfria:
type OptionalUserProfile = {
[K in keyof UserProfile]?: UserProfile[K];
};
OptionalUserProfile kommer att ha alla egenskaper frÄn UserProfile, men varje egenskap kommer att vara valfri.
Modifiera egenskapstyper
Du kan ocksÄ modifiera typen för varje egenskap. Till exempel kan du omvandla alla egenskaper till att vara strÀngar:
type StringifiedUserProfile = {
[K in keyof UserProfile]: string;
};
I det hÀr fallet kommer alla egenskaper i StringifiedUserProfile att vara av typen string.
Vad Àr Conditional Types?
Conditional Types lÄter dig definiera typer som beror pÄ ett villkor. De ger ett sÀtt att uttrycka typrelationer baserat pÄ om en typ uppfyller ett visst krav. Detta liknar en ternÀr operator i JavaScript, men för typer.
GrundlÀggande syntax
Syntaxen för en Conditional Type Àr som följer:
T extends U ? X : Y
T: Typen som kontrolleras.U: Typen somTutökar (villkoret).X: Typen som ska returneras omTextendsU(villkoret Àr sant).Y: Typen som ska returneras omTinte extendsU(villkoret Àr falskt).
Praktiska exempel
Avgöra om en typ Àr en strÀng
LÄt oss skapa en typ som returnerar string om indatatypen Àr en strÀng, och number i annat fall:
type StringOrNumber<T> = T extends string ? string : number;
type Result1 = StringOrNumber<string>; // string
type Result2 = StringOrNumber<number>; // number
type Result3 = StringOrNumber<boolean>; // number
Extrahera typ frÄn en union
Du kan anvÀnda villkorliga typer för att extrahera en specifik typ frÄn en union-typ. Till exempel för att extrahera icke-nullbara typer:
type NonNullable<T> = T extends null | undefined ? never : T;
type Result4 = NonNullable<string | null | undefined>; // string
HÀr, om T Àr null eller undefined, blir typen never, vilket sedan filtreras bort av TypeScript's förenkling av union-typer.
HĂ€rleda typer (Inferring Types)
Villkorliga typer kan ocksÄ anvÀndas för att hÀrleda typer med nyckelordet infer. Detta gör att du kan extrahera en typ frÄn en mer komplex typstruktur.
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function myFunction(x: number): string {
return x.toString();
}
type Result5 = ReturnType<typeof myFunction>; // string
I det hÀr exemplet extraherar ReturnType returtypen frÄn en funktion. Den kontrollerar om T Àr en funktion som tar vilka argument som helst och returnerar en typ R. Om den Àr det, returnerar den R; annars returnerar den any.
Kombinera Mapped Types och Conditional Types
Den verkliga kraften med Mapped Types och Conditional Types kommer frÄn att kombinera dem. Detta gör att du kan skapa mycket flexibla och uttrycksfulla typomvandlingar.
Exempel: Deep Readonly
Ett vanligt anvÀndningsfall Àr att skapa en typ som gör alla egenskaper i ett objekt, inklusive nÀstlade egenskaper, skrivskyddade. Detta kan uppnÄs med en rekursiv villkorlig typ.
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
interface Company {
name: string;
address: {
street: string;
city: string;
};
}
type ReadonlyCompany = DeepReadonly<Company>;
HÀr applicerar DeepReadonly rekursivt readonly-modifieraren pÄ alla egenskaper och deras nÀstlade egenskaper. Om en egenskap Àr ett objekt anropar den rekursivt DeepReadonly pÄ det objektet. Annars applicerar den helt enkelt readonly-modifieraren pÄ egenskapen.
Exempel: Filtrera egenskaper efter typ
LÄt oss sÀga att du vill skapa en typ som bara inkluderar egenskaper av en specifik typ. Du kan kombinera Mapped Types och Conditional Types för att uppnÄ detta.
type FilterByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Person {
name: string;
age: number;
isEmployed: boolean;
}
type StringProperties = FilterByType<Person, string>; // { name: string; }
type NonStringProperties = Omit<Person, keyof StringProperties>;
I detta exempel itererar FilterByType över egenskaperna i T och kontrollerar om typen för varje egenskap utökar U. Om den gör det, inkluderas egenskapen i den resulterande typen; annars exkluderas den genom att mappa nyckeln till never. Notera anvÀndningen av "as" för att mappa om nycklar. Vi anvÀnder sedan `Omit` och `keyof StringProperties` för att ta bort strÀngegenskaperna frÄn det ursprungliga interfacet.
Avancerade anvÀndningsfall och mönster
Utöver de grundlÀggande exemplen kan Mapped Types och Conditional Types anvÀndas i mer avancerade scenarier för att skapa mycket anpassningsbara och typsÀkra applikationer.
Distributiva villkorliga typer
Villkorliga typer Àr distributiva nÀr typen som kontrolleras Àr en union-typ. Detta innebÀr att villkoret tillÀmpas pÄ varje medlem i unionen individuellt, och resultaten kombineras sedan till en ny union-typ.
type ToArray<T> = T extends any ? T[] : never;
type Result6 = ToArray<string | number>; // string[] | number[]
I detta exempel tillÀmpas ToArray pÄ varje medlem i unionen string | number individuellt, vilket resulterar i string[] | number[]. Om villkoret inte hade varit distributivt skulle resultatet ha varit (string | number)[].
AnvÀnda Utility Types
TypeScript tillhandahÄller flera inbyggda "utility types" som utnyttjar Mapped Types och Conditional Types. Dessa "utility types" kan anvÀndas som byggstenar för mer komplexa typomvandlingar.
Partial<T>: Gör alla egenskaper iTvalfria.Required<T>: Gör alla egenskaper iTobligatoriska.Readonly<T>: Gör alla egenskaper iTskrivskyddade.Pick<T, K>: VÀljer en uppsÀttning egenskaperKfrÄnT.Omit<T, K>: Tar bort en uppsÀttning egenskaperKfrÄnT.Record<K, T>: Konstruerar en typ med en uppsÀttning egenskaperKav typenT.Exclude<T, U>: Exkluderar frÄnTalla typer som Àr tilldelningsbara tillU.Extract<T, U>: Extraherar frÄnTalla typer som Àr tilldelningsbara tillU.NonNullable<T>: ExkluderarnullochundefinedfrÄnT.Parameters<T>: HÀmtar parametrarna för en funktionstypT.ReturnType<T>: HÀmtar returtypen för en funktionstypT.InstanceType<T>: HÀmtar instanstypen för en konstruktorfunktionstypT.
Dessa "utility types" Àr kraftfulla verktyg som kan förenkla komplexa typhanteringar. Du kan till exempel kombinera Pick och Partial för att skapa en typ som gör endast vissa egenskaper valfria:
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface Product {
id: number;
name: string;
price: number;
description: string;
}
type OptionalDescriptionProduct = Optional<Product, "description">;
I detta exempel har OptionalDescriptionProduct alla egenskaper frÄn Product, men egenskapen description Àr valfri.
AnvÀnda Template Literal Types
Template Literal Types lÄter dig skapa typer baserade pÄ strÀngliteraler. De kan anvÀndas i kombination med Mapped Types och Conditional Types för att skapa dynamiska och uttrycksfulla typomvandlingar. Till exempel kan du skapa en typ som lÀgger till ett prefix till alla egenskapsnamn med en specifik strÀng:
type Prefix<T, P extends string> = {
[K in keyof T as `${P}${string & K}`]: T[K];
};
interface Settings {
apiUrl: string;
timeout: number;
}
type PrefixedSettings = Prefix<Settings, "data_">;
I detta exempel kommer PrefixedSettings att ha egenskaperna data_apiUrl och data_timeout.
BÀsta praxis och övervÀganden
- HĂ„ll det enkelt: Ăven om Mapped Types och Conditional Types Ă€r kraftfulla kan de ocksĂ„ göra din kod mer komplex. Försök att hĂ„lla dina typomvandlingar sĂ„ enkla som möjligt.
- AnvÀnd Utility Types: Utnyttja TypeScript's inbyggda "utility types" nÀr det Àr möjligt. De Àr vÀltestade och kan förenkla din kod.
- Dokumentera dina typer: Dokumentera tydligt dina typomvandlingar, sÀrskilt om de Àr komplexa. Detta hjÀlper andra utvecklare att förstÄ din kod.
- Testa dina typer: AnvÀnd TypeScript's typkontroll för att sÀkerstÀlla att dina typomvandlingar fungerar som förvÀntat. Du kan skriva enhetstester för att verifiera beteendet hos dina typer.
- TÀnk pÄ prestanda: Komplexa typomvandlingar kan pÄverka prestandan hos din TypeScript-kompilator. Var medveten om komplexiteten i dina typer och undvik onödiga berÀkningar.
Slutsats
Mapped Types och Conditional Types Àr kraftfulla funktioner i TypeScript som gör det möjligt för dig att skapa mycket flexibla och uttrycksfulla typomvandlingar. Genom att bemÀstra dessa koncept kan du förbÀttra typsÀkerheten, underhÄllbarheten och den övergripande kvaliteten pÄ dina TypeScript-applikationer. FrÄn enkla omvandlingar som att göra egenskaper valfria eller skrivskyddade till komplexa rekursiva omvandlingar och villkorlig logik, ger dessa funktioner de verktyg du behöver för att bygga robusta och skalbara applikationer. FortsÀtt att utforska och experimentera med dessa funktioner för att lÄsa upp deras fulla potential och bli en mer skicklig TypeScript-utvecklare.
NÀr du fortsÀtter din TypeScript-resa, kom ihÄg att utnyttja den mÀngd resurser som finns tillgÀngliga, inklusive den officiella TypeScript-dokumentationen, online-communities och open source-projekt. Omfamna kraften i Mapped Types och Conditional Types, och du kommer att vara vÀl rustad för att ta itu med Àven de mest utmanande typrelaterade problemen.